package com.element.oauth2.server.global.converter;

import com.element.oauth2.constant.SecurityParams;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public final class OAuthAuthorizationConsentRequestConverter implements AuthenticationConverter {

    private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));

    public OAuthAuthorizationConsentRequestConverter() {
    }

    public Authentication convert(HttpServletRequest request) {
        if ("POST".equals(request.getMethod()) && request.getParameter(SecurityParams.RESPONSE_TYPE) == null) {
            MultiValueMap<String, String> parameters = getParameters(request);
            String authorizationUri = request.getRequestURL().toString();
            String clientId = parameters.getFirst(SecurityParams.CLIENT_ID);
            if (!StringUtils.hasText(clientId) || (parameters.get(SecurityParams.CLIENT_ID)).size() != 1) {
                throwError();
            }

            Authentication principal = SecurityContextHolder.getContext().getAuthentication();
            if (principal == null) {
                principal = ANONYMOUS_AUTHENTICATION;
            }

            String state = "";
            if (parameters.containsKey(SecurityParams.STATE)) {
                state = parameters.getFirst(SecurityParams.STATE);
            }

            Set<String> scopes = new HashSet<>();
            if (parameters.containsKey(SecurityParams.SCOPE)) {
                scopes = new HashSet<>(parameters.get(SecurityParams.SCOPE));
            }

            Map<String, Object> additionalParameters = new HashMap<>();
            parameters.forEach((key, value) -> {
                if (!key.equals(SecurityParams.CLIENT_ID) && !key.equals(SecurityParams.STATE) && !key.equals(SecurityParams.SCOPE)) {
                    additionalParameters.put(key, value.size() == 1 ? value.get(0) : value.toArray(new String[0]));
                }

            });
            return new OAuth2AuthorizationConsentAuthenticationToken(authorizationUri, clientId, principal, state, scopes, additionalParameters);
        } else {
            return null;
        }
    }

    private void throwError() {
        OAuth2Error error = new OAuth2Error("invalid_request", "OAuth 2.0 Parameter: " + SecurityParams.CLIENT_ID, null);
        throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
    }

    private MultiValueMap<String, String> getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            for (String value : values) {
                parameters.add(key, value);
            }
        });
        return parameters;
    }
}